#ifndef LDFDECTOR_H_
#define LDFDECTOR_H_
#include <iostream>
#include <functional>
#include <unordered_map>
#include <PcapFileDevice.h>
#include <RawPacket.h>
#include <Packet.h>
#include <IPv4Layer.h>
#include <TcpLayer.h>

struct Options {
	std::string input_file;
	std::string output_file;
	__time_t LDF_threshold;
	__time_t max_interval;
	__time_t check_interval;
	bool slience;
};

struct PacketInfo {
    uint32_t src_ip;
    uint32_t dst_ip;
    uint16_t src_port;
    uint16_t dst_port;
	timespec ts;
};

struct FlowKey{
    uint32_t src_ip;
    uint32_t dst_ip;
    uint16_t src_port;
    uint16_t dst_port;

	bool operator == (const FlowKey& fk) const {
		return fk.src_ip == src_ip && fk.dst_ip == dst_ip && fk.src_port == src_port && fk.dst_port == dst_port;
	}

	friend std::ostream & operator << (std::ostream& os, const FlowKey& fk) {
		os << fk.src_ip << " " << fk.src_port << " " << fk.dst_ip << " " << fk.dst_port;
		return os;
	}
};

struct FlowValue{
	timespec start_time;
	timespec end_time;
	unsigned int packet_num;

	friend std::ostream & operator << (std::ostream& os, const FlowValue& fv) {
		os << fv.start_time.tv_sec << " " 
		   << fv.end_time.tv_sec   << " "
		   << fv.end_time.tv_sec - fv.start_time.tv_sec << " "
		   << fv.packet_num;
		return os;
	}
};

struct FlowKeyHash{
	size_t operator()(const FlowKey & flow_key) const{
		return std::hash<uint32_t>()(flow_key.src_ip) ^ std::hash<uint32_t>()(flow_key.src_ip) ^ 
		std::hash<uint16_t>()(flow_key.src_port) ^ std::hash<uint16_t>()(flow_key.dst_port);
	}
};


// print all requested packets in a pcap file
int PrintPcapPackets(pcpp::PcapFileReaderDevice* reader, std::ostream* out, int packetCount);

// Verify the validity of the file path
bool ValidateFilePath(const char* file_path, const std::string& value);

// Get packets' information, including src_IP, src_port, dst_IP, dst_port, protocol and timestamp
int PcapPacketsHandler(pcpp::PcapFileReaderDevice* reader, std::string input_file);

// Print all requested packets in a pcap file
int PrintPcapPackets(pcpp::PcapFileReaderDevice* reader, std::ostream* out, int packetCount);

// Get src_IP, src_port, dst_IP, dst_port, protocol, timestemp from a raw packet
int PacketParser(pcpp::RawPacket& rawPacket, PacketInfo& flow);

// Cover PacketInfo to FlowKey
FlowKey GenFlowKey(const PacketInfo& packet_info);

// Update FlowValue
void UpdateFlowValue(const PacketInfo& packet_info, std::unordered_map<FlowKey, FlowValue, FlowKeyHash>& m_flow, const FlowKey& flow_key);

// Insert flow
void InsertFlow(const PacketInfo& packet_info, std::unordered_map<FlowKey, FlowValue, FlowKeyHash>& m_flow, const FlowKey& flow_key);

// Show keys and values form map
void ShowMap(std::unordered_map<FlowKey, FlowValue, FlowKeyHash>& m_flow);

// Determine whether the checking time node is reached and update next check time
bool TimeToCheck(const timespec& cur_time, timespec& check_time, const __time_t& interval);

// Update FlowValue
void UpdateFlowValue(std::unordered_map<FlowKey, FlowValue, FlowKeyHash>& m_flow, timespec cur_time);

// Report LDF and delete not LDF
void UpdateFlowMap(std::unordered_map<FlowKey, FlowValue, FlowKeyHash>& m_flow, timespec cur_time, Options& opts, std::ostream& os);

// Get first packet's timestamp
timespec GetStartTime(const char* file_path);

// Find out and output LDF from map
void LastCheck(const std::unordered_map<FlowKey, FlowValue, FlowKeyHash>& m_flow, const Options& opts, std::ostream& os);

// Get src_IP and dst_IP from IP layer
int IPv4LayerHandler(const pcpp::Packet parsedPacket, PacketInfo& flow);

// Get src_port and dst_port from TCP layer
int TCPLayerHandler(const pcpp::Packet parsedPacket, PacketInfo& flow);

// Cover IP address from bytes to int (Notice: network byte order is different from host)
uint32_t IPv4BytesToInt(const uint8_t* bytes);
#endif